/*
 * Copyright (c) 2023-2025 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.common.security.configurer;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.elsfs.cloud.common.properties.ElsfsSecurityProperties;
import org.elsfs.cloud.common.util.http.RequestMethod;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.DefaultServerRedirectStrategy;
import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter;
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter;
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import reactor.core.publisher.Mono;

/**
 * 配置安全 reactive,
 *
 * @author zeng
 */
public abstract non-sealed class AbstractReactiveSecurityCustomConfiguration
    extends AbstractSecurityCustomConfiguration<ServerHttpSecurity>
    implements ApplicationContextAware {
  ApplicationContext applicationContext;

  /**
   * 退出配置
   *
   * @param http http
   */
  @Override
  protected void autoConfigurationOauthLogout(ServerHttpSecurity http) {
    // @formatter:off
    http.logout(
        logout ->
            logout
                .logoutHandler(
                    (exchange, authentication) -> {
                      System.out.println(authentication.getAuthorities());
                      return Mono.empty();
                    })
                // 退出登录的url
                .logoutUrl("/logout")
                .logoutSuccessHandler(
                    (exchange, authentication) -> {
                      System.out.println(authentication.getAuthorities());
                      return Mono.empty();
                    }));
    // @formatter:on
  }

  /**
   * 添加安全的响应头
   *
   * @param http http
   * @throws Exception 配置异常
   */
  @Override
  protected void addHttpSecurityHeaders(ServerHttpSecurity http) throws Exception {
    // @formatter:off
    http.headers(
        headers ->
            headers
                // 缓存控制
                // Cache-Control: no-cache, no-store, max-age=0, must-revalidate
                // Pragma: no-cache
                // Expires: 0
                .cache(ServerHttpSecurity.HeaderSpec.CacheSpec::disable)
                // 内容嗅探  默认   X-Content-Type-Options: nosniff
                .contentTypeOptions(ServerHttpSecurity.HeaderSpec.ContentTypeOptionsSpec::disable)
                //  X-Frame-Options: DENY
                //  让你的网站被添加到一个 frame
                // 中可能是一个安全问题。例如，通过使用巧妙的CSS样式设计，用户可能会被骗去点击他们不打算点击的东西。例如，一个登录了银行的用户可能会点击一个授予其他用户访问权限的按钮。这类攻击被称为
                // 点击劫持（Clickjacking）。
                .frameOptions(
                    frameOptionsSpec ->
                        frameOptionsSpec.mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN))
                //  内容安全策略（Content Security Policy）
                // （CSP）是一种机制，web应用可以用来缓解内容注入漏洞，如跨站脚本（XSS）。CSP是一种声明性的策略，为web应用程序作者提供了一种设施，
                // 以声明并最终告知客户端（user-agent）web应用程序期望加载资源的来源。
                // 一个web应用程序可以通过在响应中包括以下HTTP头信息之一来使用CSP。
                // Content-Security-Policy 和Content-Security-Policy-Report-Only

                //        .contentSecurityPolicy(contentSecurityPolicy -> contentSecurityPolicy
                //                          .policyDirectives("")
                //                          .reportOnly()
                //                      )
                // Referrer Policy 是一种机制，Web应用程序可以用来管理 referrer 字段，它包含用户最后浏览的页面。
                // Referrer-Policy: same-origin
                .referrerPolicy(
                    referrerPolicyConfig ->
                        referrerPolicyConfig.policy(
                            ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy.NO_REFERRER)));
    // @formatter:on

  }

  /**
   * 配置 认证的请求
   *
   * @param http http
   * @throws Exception 配置异常
   */
  @Override
  protected void addHttpSecurityAuthorizeHttpRequests(ServerHttpSecurity http) throws Exception {
    ElsfsSecurityProperties properties = applicationContext.getBean(ElsfsSecurityProperties.class);
    Map<RequestMethod, HashSet<String>> ignoreHttpMethod = properties.getIgnoreHttpMethod();
    List<ServerWebExchangeMatcher> matchers = new ArrayList<>();
    matchers.add(
        exchange ->
            (HttpMethod.OPTIONS.matches(exchange.getRequest().getMethod().name())
                    && exchange.getRequest().getHeaders().getOrigin() != null
                    && exchange.getRequest().getHeaders().getAccessControlRequestMethod() != null)
                ? ServerWebExchangeMatcher.MatchResult.match()
                : ServerWebExchangeMatcher.MatchResult.notMatch());

    for (RequestMethod method : ignoreHttpMethod.keySet()) {
      String name = method.name();
      for (String s : ignoreHttpMethod.get(method)) {
        matchers.add(new PathPatternParserServerWebExchangeMatcher(s, HttpMethod.valueOf(name)));
      }
    }
    http.authorizeExchange(
        (authorize) ->
            authorize
                .matchers(new OrServerWebExchangeMatcher(matchers))
                .permitAll()
                .anyExchange()
                .authenticated());
  }

  /**
   * oath2Login 配置
   *
   * @param http h
   * @throws Exception e
   */
  @Override
  protected void autoConfigurationOauthLogin(ServerHttpSecurity http) throws Exception {

    ObjectProvider<OAuth2ClientProperties> auth2ClientPropertiesObjectProvider =
        applicationContext.getBeanProvider(OAuth2ClientProperties.class);
    OAuth2ClientProperties properties =
        auth2ClientPropertiesObjectProvider.getIfUnique(OAuth2ClientProperties::new);
    // @formatter:off
    if (!properties.getRegistration().isEmpty()) {
      http.oauth2Login(
          oauth2Login -> {
            // 登录重定向地址
            oauth2Login.authorizationRedirectStrategy(new DefaultServerRedirectStrategy());
          });
      //  端点配置

      // @formatter:on
    }
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) {
    this.applicationContext = applicationContext;
  }
}
